package http

import (
	"bytes"
	"encoding/json"
	"github.com/dop251/goja"
	"github.com/dop251/goja_nodejs/require"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

type Module struct {
	runtime *goja.Runtime
}

type Response struct {
	Body       string
	Headers    map[string][]string
	StatusCode int
}

func (h Module) Get(url string, headers map[string]string, result interface{}) Response {
	if result != nil {
		go func() {
			resultFn := result.(func(goja.FunctionCall) goja.Value)
			response := h.request("GET", url, headers, nil)
			resultFn(goja.FunctionCall{
				Arguments: []goja.Value{h.runtime.ToValue(response)},
			})
		}()
		return Response{}
	}
	return h.request("GET", url, headers, nil)
}

func (h Module) Post(url string, headers map[string]string, body interface{}, result interface{}) Response {
	var data io.Reader = nil
	for k, v := range headers {
		if strings.EqualFold(k, "ContentType") {
			if strings.Contains(v, "json") {
				json, err := json.Marshal(body)
				if err != nil {
					panic(err)
				}
				data = bytes.NewBuffer(json)
			} else {
				data = strings.NewReader(body.(string))
			}
			break
		}
	}
	if result != nil {
		go func() {
			resultFn := result.(func(goja.FunctionCall) goja.Value)
			response := h.request("POST", url, headers, data)
			resultFn(goja.FunctionCall{
				Arguments: []goja.Value{h.runtime.ToValue(response)},
			})
		}()
		return Response{}
	}
	return h.request("POST", url, headers, data)
}

func (h Module) PostFile(url string, headers map[string]string, params string, files map[string]string, result interface{}) Response {
	data := new(bytes.Buffer)
	writer := multipart.NewWriter(data)
	for k, v := range files {
		formFile, err := writer.CreateFormFile(k, filepath.Base(v))
		if err != nil {
			panic(err)
		}
		op, err1 := os.Open(v)
		if err1 != nil {
			panic(err1)
		}
		_, err = io.Copy(formFile, op)
		if err != nil {
			panic(err)
		}
	}
	for _, val := range strings.Split(params, "&") {
		split := strings.Split(val, "=")
		println(split[0], split[1])
		_ = writer.WriteField(split[0], split[1])
	}
	errClose := writer.Close()
	if errClose != nil {
		panic(errClose)
	}
	headers["ContentType"] = writer.FormDataContentType()
	for k, _ := range headers {
		if strings.EqualFold(k, "ContentType") {
			headers[k] = writer.FormDataContentType()
			break
		}
	}
	if result != nil {
		go func() {
			resultFn := result.(func(goja.FunctionCall) goja.Value)
			response := h.request("POST", url, headers, data)
			resultFn(goja.FunctionCall{
				Arguments: []goja.Value{h.runtime.ToValue(response)},
			})
		}()
		return Response{}
	}

	return h.request("POST", url, headers, data)
}

func (h Module) request(method, url string, headers map[string]string, reqBody io.Reader) Response {
	request, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		panic(err)
	}
	for k, v := range headers {
		request.Header.Set(k, v)
	}
	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	return h.wrapResponse(resp)
}

func (h Module) wrapResponse(resp *http.Response) Response {
	body, err := io.ReadAll(resp.Body)

	if err != nil {
		panic(err)
	}

	return Response{
		Body:       string(body),
		StatusCode: resp.StatusCode,
		Headers:    resp.Header,
	}
}

func Require(runtime *goja.Runtime, module *goja.Object) {
	h := &Module{
		runtime: runtime,
	}
	obj := module.Get("exports").(*goja.Object)
	obj.Set("Get", h.Get)
	obj.Set("Post", h.Post)
	obj.Set("PostFile", h.PostFile)
}

func Enable(runtime *goja.Runtime) {
	runtime.Set("http", require.Require(runtime, "http"))
}
